﻿import L from "leaflet"

L.latlngGraticule = L.Layer.extend({
    // 默认样式,修改请在外部配置
    options: {
        showLabel: true,
        opacity: 1,
        weight: 0.8,
        dash: [5, 5],
        color: 'rgb(87,78,78)',
        fontColor: 'rgb(0,0,0)',
        font: 'bold 54px Verdana',
        lngLineCurved: 0,
        latLineCurved: 0,
        zIndex: 999,
        ratio: 1,
        padding: 10,
        zoomInterval: [
            { start: 2, end: 2, interval: 30 },
            { start: 3, end: 4, interval: 20 },
            { start: 5, end: 5, interval: 10 },
            { start: 6, end: 6, interval: 5 },
            { start: 7, end: 7, interval: 2 },
            { start: 8, end: 8, interval: 1 },
            { start: 9, end: 10, interval: 0.5 },
            { start: 11, end: 11, interval: 0.2 }
        ]
    },

    /**设置经纬线样式 */
    _setStyle: function (color, dash) {
        let self = this;
        this.options.color = color;
        this.options.fontColor = color;
        this.options.dash = dash;
        if (!this._map) {
            return;
        }
        self.__draw(true);
    },

    initialize: function (options) {
        L.setOptions(this, options);

        let defaultFontName = ''// 'Verdana';
        let _ff = this.options.font.split(' ');
        if (_ff.length < 2) {
            this.options.font += ' ' + defaultFontName;
        }

        if (!this.options.fontColor) {
            this.options.fontColor = this.options.color;
        }

        if (this.options.zoomInterval) {
            if (this.options.zoomInterval.latitude) {
                this.options.latInterval = this.options.zoomInterval.latitude;
                if (!this.options.zoomInterval.longitude) {
                    this.options.lngInterval = this.options.zoomInterval.latitude;
                }
            }
            if (this.options.zoomInterval.longitude) {
                this.options.lngInterval = this.options.zoomInterval.longitude;
                if (!this.options.zoomInterval.latitude) {
                    this.options.latInterval = this.options.zoomInterval.longitude;
                }
            }
            if (!this.options.latInterval) {
                this.options.latInterval = this.options.zoomInterval;
            }
            if (!this.options.lngInterval) {
                this.options.lngInterval = this.options.zoomInterval;
            }
        }
    },

    onAdd: function (map) {
        this._map = map;
        if (!this._container) {
            this._initCanvas();
        }
        map._panes.overlayPane.appendChild(this._container);
        map.on('viewreset', this._reset, this);
        map.on('move', this._reset, this);
        map.on('moveend', this._reset, this);
        if (map.options.zoomAnimation && L.Browser.any3d) {
            map.on('zoomanim', this._animateZoom, this);
        }
        this._reset();
    },

    onRemove: function (map) {
        map.getPanes().overlayPane.removeChild(this._container);
        map.off('viewreset', this._reset, this);
        map.off('move', this._reset, this);
        map.off('moveend', this._reset, this);
        if (map.options.zoomAnimation) {
            map.off('zoomanim', this._animateZoom, this);
        }
    },

    addTo: function (map) {
        map.addLayer(this);
        return this;
    },

    setOpacity: function (opacity) {
        this.options.opacity = opacity;
        this._updateOpacity();
        return this;
    },

    bringToFront: function () {
        if (this._canvas) {
            this._map._panes.overlayPane.appendChild(this._canvas);
        }
        return this;
    },

    bringToBack: function () {
        let pane = this._map._panes.overlayPane;
        if (this._canvas) {
            pane.insertBefore(this._canvas, pane.firstChild);
        }
        return this;
    },

    getAttribution: function () {
        return this.options.attribution;
    },

    _initCanvas: function () {
        this._container = L.DomUtil.create('div', 'leaflet-image-layer');
        this._canvas = L.DomUtil.create('canvas', '');
        this._setZindex(this.options.zIndex);
        if (this._map.options.zoomAnimation && L.Browser.any3d) {
            L.DomUtil.addClass(this._canvas, 'leaflet-zoom-animated');
        } else {
            L.DomUtil.addClass(this._canvas, 'leaflet-zoom-hide');
        }
        if (this.options.className)
            L.DomUtil.addClass(this._canvas, this.options.className);
        this._updateOpacity();
        this._container.appendChild(this._canvas);
        L.extend(this._canvas, {
            onselectstart: L.Util.falseFn,
            onmousemove: L.Util.falseFn,
            onload: L.bind(this._onCanvasLoad, this)
        });
    },

    _animateZoom: function (e) {
        let map = this._map,
            container = this._container,
            canvas = this._canvas,
            scale = map.getZoomScale(e.zoom),
            nw = map.containerPointToLatLng([0, 0]),
            se = map.containerPointToLatLng([canvas.width, canvas.height]),
            topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
            size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
            origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));
    },

    _setZindex: function (Zindex) {
        this._container.style.zIndex = Zindex;
    },

    _reset: function () {
        let container = this._container,
            canvas = this._canvas,
            size = this._map.getSize(),
            lt = this._map.containerPointToLayerPoint([0, 0]);
        L.DomUtil.setPosition(container, lt);
        container.style.width = size.x + 'px';
        container.style.height = size.y + 'px';
        canvas.width = size.x * this.options.ratio;
        canvas.height = size.y * this.options.ratio;
        canvas.style.width = size.x + 'px';
        canvas.style.height = size.y + 'px';
        this.__calcInterval();
        this.__draw(true);
    },

    _onCanvasLoad: function () {
        this.fire('load');
    },

    _updateOpacity: function () {
        L.DomUtil.setOpacity(this._canvas, this.options.opacity);
    },

    __format_lat: function (lat) {
        if (this.options.latFormatTickLabel) {
            return this.options.latFormatTickLabel(lat);
        }
        if (lat < 0) {
            return '' + (lat * -1) + 'S';
        } else if (lat > 0) {
            return '' + lat + 'N';
        }
        return '' + lat;
    },

    __format_lng: function (lng) {
        if (this.options.lngFormatTickLabel) {
            return this.options.lngFormatTickLabel(lng);
        }
        if (lng > 180) {
            return '' + (360 - lng) + 'W';
        } else if (lng > 0 && lng < 180) {
            return '' + lng + 'E';
        } else if (lng < 0 && lng > -180) {
            return '' + (lng * -1) + 'W';
        } else if (lng == -180) {
            return '' + (lng * -1);
        } else if (lng < -180) {
            return '' + (360 + lng) + 'W';
        }
        return '' + lng;
    },

    __calcInterval: function () {
        let zoom = this._map.getZoom();
        if (this._currZoom != zoom) {
            this._currLngInterval = 0;
            this._currLatInterval = 0;
            this._currZoom = zoom;
        }
        if (!this._currLngInterval) {
            try {
                for (let idx in this.options.lngInterval) {
                    let dict = this.options.lngInterval[idx];
                    if (dict.start <= zoom) {
                        if (dict.end && dict.end >= zoom) {
                            this._currLngInterval = dict.interval;
                            break;
                        }
                    }
                }
            } catch (e) {
                this._currLngInterval = 0;
            }
        }
        if (!this._currLatInterval) {
            try {
                for (let idx in this.options.latInterval) {
                    let dict = this.options.latInterval[idx];
                    if (dict.start <= zoom) {
                        if (dict.end && dict.end >= zoom) {
                            this._currLatInterval = dict.interval;
                            break;
                        }
                    }
                }
            } catch (e) {
                this._currLatInterval = 0;
            }
        }
    },

    __draw: function (label) {
        function _parse_px_to_int(txt) {
            if (txt.length > 2) {
                if (txt.charAt(txt.length - 2) == 'p') {
                    txt = txt.substr(0, txt.length - 2);
                }
            }
            try {
                return parseInt(txt, 10);
            } catch (e) {
                console.log(e)
            }
            return 0;
        }

        let self = this,
            canvas = this._canvas,
            map = this._map,
            curvedLon = this.options.lngLineCurved,
            curvedLat = this.options.latLineCurved;

        if (L.Browser.canvas && map) {
            if (!this._currLngInterval || !this._currLatInterval) {
                this.__calcInterval();
            }

            let latInterval = this._currLatInterval, lngInterval = this._currLngInterval;

            let ctx = canvas.getContext('2d');
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.lineWidth = this.options.weight;
            ctx.strokeStyle = this.options.color;
            ctx.fillStyle = this.options.fontColor;
            ctx.setLineDash(this.options.dash);
            if (this.options.font) {
                ctx.font = this.options.font;
            }
            let txtWidth = ctx.measureText('0').width;
            let txtHeight = 12;
            try {
                let _font_size = ctx.font.split(' ')[1];
                txtHeight = _parse_px_to_int(_font_size);
            } catch (e) {
                console.log(e);
            }

            let ww = document.body.clientWidth, hh = document.body.clientHeight;

            let lt = map.containerPointToLatLng(L.point(0, 0));
            let rt = map.containerPointToLatLng(L.point(ww, 0));
            let rb = map.containerPointToLatLng(L.point(ww, hh));

            let _lat_b = rb.lat, _lat_t = lt.lat;
            let _lon_l = lt.lng, _lon_r = rt.lng;

            let _point_per_lat = (_lat_t - _lat_b) / (hh * 0.2);
            if (isNaN(_point_per_lat)) {
                return;
            }

            if (_point_per_lat < 1) {
                _point_per_lat = 1;
            }
            if (_lat_b < -90) {
                _lat_b = -90;
            } else {
                _lat_b = parseInt(_lat_b - _point_per_lat, 10);
            }

            if (_lat_t > 90) {
                _lat_t = 90;
            } else {
                _lat_t = parseInt(_lat_t + _point_per_lat, 10);
            }

            let _point_per_lon = (_lon_r - _lon_l) / (ww * 0.2);
            if (_point_per_lon < 1) {
                _point_per_lon = 1;
            }
            if (_lon_l > 0 && _lon_r < 0) {
                _lon_r += 360;
            }
            _lon_r = parseInt(_lon_r + _point_per_lon, 10);
            _lon_l = parseInt(_lon_l - _point_per_lon, 10);

            let ll, latstr, lngstr, _lon_delta = 0.5;

            let __draw_lat_line = (self, lat_tick) => {
                ll = self._latLngToCanvasPoint(L.latLng(lat_tick, _lon_l));
                latstr = self.__format_lat(lat_tick);
                txtWidth = ctx.measureText(latstr).width;

                if (curvedLat) {
                    if (typeof (curvedLat) == 'number') {
                        _lon_delta = curvedLat;
                    }

                    let __lon_left = _lon_l,
                        __lon_right = _lon_r;
                    if (ll.x > 0) {
                        let __lon_left = map.containerPointToLatLng(L.point(0, ll.y));
                        __lon_left = __lon_left.lng - _point_per_lon;
                        ll.x = 0;
                    }
                    let rr = self._latLngToCanvasPoint(L.latLng(lat_tick, __lon_right));
                    if (rr.x < ww) {
                        __lon_right = map.containerPointToLatLng(L.point(ww, rr.y));
                        __lon_right = __lon_right.lng + _point_per_lon;
                        if (__lon_left > 0 && __lon_right < 0) {
                            __lon_right += 360;
                        }
                    }

                    ctx.beginPath();
                    ctx.moveTo(ll.x, ll.y);
                    let _prev_p = null;
                    for (let j = __lon_left; j <= __lon_right; j += _lon_delta) {
                        rr = self._latLngToCanvasPoint(L.latLng(lat_tick, j));
                        ctx.lineTo(rr.x, rr.y);

                        if (self.options.showLabel && label && _prev_p != null) {
                            if (_prev_p.x < 0 && rr.x >= 0) {
                                let _s = (rr.x - 0) / (rr.x - _prev_p.x);
                                let _y = rr.y - ((rr.y - _prev_p.y) * _s);
                                ctx.fillText(latstr, 0, _y + (txtHeight / 2));
                            } else if (_prev_p.x <= (ww - txtWidth) && rr.x > (ww - txtWidth)) {
                                let _s = (rr.x - ww) / (rr.x - _prev_p.x);
                                let _y = rr.y - ((rr.y - _prev_p.y) * _s);
                                ctx.fillText(latstr, ww - txtWidth, _y + (txtHeight / 2) - 2);
                            }
                        }

                        _prev_p = {
                            x: rr.x,
                            y: rr.y,
                            lon: j,
                            lat: lat_tick
                        };
                    }
                    ctx.stroke();
                } else {
                    let __lon_right = _lon_r;
                    let rr = self._latLngToCanvasPoint(L.latLng(lat_tick, __lon_right));
                    if (curvedLon) {
                        __lon_right = map.containerPointToLatLng(L.point(0, rr.y));
                        __lon_right = __lon_right.lng;
                        rr = self._latLngToCanvasPoint(L.latLng(lat_tick, __lon_right));

                        let __lon_left = map.containerPointToLatLng(L.point(ww, rr.y));
                        __lon_left = __lon_left.lng;
                        ll = self._latLngToCanvasPoint(L.latLng(lat_tick, __lon_left));
                    }
                    ctx.beginPath();
                    ctx.moveTo(ll.x + 1, ll.y * self.options.ratio);
                    ctx.lineTo((rr.x - 1) * self.options.ratio, rr.y * self.options.ratio);
                    ctx.stroke();
                    if (self.options.showLabel && label) {
                        let _yy = ll.y + (txtHeight / 2);
                        ctx.fillText(latstr, self.options.padding, _yy * self.options.ratio - 85);
                        ctx.fillText(latstr, ww * self.options.ratio - txtWidth - self.options.padding, _yy * self.options.ratio - 85);
                    }
                }
            }

            if (latInterval > 0) {
                for (let i = latInterval; i <= _lat_t; i += latInterval) {
                    i = parseFloat(i.toFixed(2));
                    if (i >= _lat_b) {
                        __draw_lat_line(this, i);
                    }
                }
                for (let i = 0; i >= _lat_b; i -= latInterval) {
                    i = parseFloat(i.toFixed(2));
                    if (i <= _lat_t) {
                        __draw_lat_line(this, i);
                    }
                }
            }

            let __draw_lon_line = (self, lon_tick) => {
                lngstr = self.__format_lng(lon_tick);
                txtWidth = ctx.measureText(lngstr).width;
                let bb = self._latLngToCanvasPoint(L.latLng(_lat_b, lon_tick));
                let _lat_delta
                if (curvedLon) {
                    if (typeof (curvedLon) == 'number') {
                        _lat_delta = curvedLon;
                    }

                    ctx.beginPath();
                    ctx.moveTo(bb.x, bb.y);
                    let _prev_p = null;
                    for (let j = _lat_b; j < _lat_t; j += _lat_delta) {
                        let tt = self._latLngToCanvasPoint(L.latLng(j, lon_tick));
                        ctx.lineTo(tt.x, tt.y);

                        if (self.options.showLabel && label && _prev_p != null) {
                            if (_prev_p.y > 8 && tt.y <= 8) {
                                ctx.fillText(lngstr, tt.x - (txtWidth / 2), txtHeight);
                            } else if (_prev_p.y >= hh && tt.y < hh) {
                                ctx.fillText(lngstr, tt.x - (txtWidth / 2), hh - 2);
                            }
                        }

                        _prev_p = {
                            x: tt.x,
                            y: tt.y,
                            lon: lon_tick,
                            lat: j
                        };
                    }
                    ctx.stroke();
                } else {
                    let __lat_top = _lat_t;
                    let tt = self._latLngToCanvasPoint(L.latLng(__lat_top, lon_tick));
                    if (curvedLat) {
                        __lat_top = map.containerPointToLatLng(L.point(tt.x, 0));
                        __lat_top = __lat_top.lat;
                        if (__lat_top > 90) {
                            __lat_top = 90;
                        }
                        tt = self._latLngToCanvasPoint(L.latLng(__lat_top, lon_tick));

                        let __lat_bottom = map.containerPointToLatLng(L.point(bb.x, hh));
                        __lat_bottom = __lat_bottom.lat;
                        if (__lat_bottom < -90) {
                            __lat_bottom = -90;
                        }
                        bb = self._latLngToCanvasPoint(L.latLng(__lat_bottom, lon_tick));
                    }

                    ctx.beginPath();
                    ctx.moveTo(tt.x * self.options.ratio, tt.y + 1);
                    ctx.lineTo(bb.x * self.options.ratio, (bb.y - 1) * self.options.ratio);
                    ctx.stroke();

                    if (self.options.showLabel && label) {
                        ctx.fillText(lngstr, tt.x * self.options.ratio - (txtWidth / 2), txtHeight + 1 + self.options.padding);
                        ctx.fillText(lngstr, bb.x * self.options.ratio - (txtWidth / 2), (hh - 2) * self.options.ratio - self.options.padding);
                    }
                }
            }

            if (lngInterval > 0) {
                for (let i = lngInterval; i <= _lon_r; i += lngInterval) {
                    i = parseFloat(i.toFixed(2));
                    if (i >= _lon_l) {
                        __draw_lon_line(this, i);
                    }
                }
                for (let i = 0; i >= _lon_l; i -= lngInterval) {
                    i = parseFloat(i.toFixed(2));
                    if (i <= _lon_r) {
                        __draw_lon_line(this, i);
                    }
                }
            }
        }
    },

    _latLngToCanvasPoint: function (latlng) {
        let map = this._map;
        let projectedPoint = map.project(L.latLng(latlng));
        projectedPoint._subtract(map.getPixelOrigin());
        return L.point(projectedPoint).add(map._getMapPanePos());
    }
})

export default function (options) {
    return new L.latlngGraticule(options)
}